#include "DX_App.h"

Application			*g_pD3DApp		= NULL;
IDirect3DDevice9	*g_pD3DDevice	= NULL;
cInput				*g_pDInput		= NULL;
cAudio				*g_pAudio		= NULL;
cGraphics			*g_pGraphics	= NULL;

//Definition of the Message Handling System / Window Procedures
LRESULT CALLBACK MsgProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {

	switch(uMsg) {
		//Triggers when the Size of the when changes
		case WM_SIZE:
			break;
		
		//Triggers when the user exits from resizing the Window
		case WM_EXITSIZEMOVE:
			break;
			
		case WM_KEYDOWN:
			if(wParam == VK_F12)
			{
				if(g_pD3DApp->getWindowed())
				{
					g_pD3DApp->setWindowed(false);
					g_pD3DApp->enableFullScreen(g_pD3DApp->getWindowed());
				} 
				else
				{
					g_pD3DApp->setWindowed(true);
					g_pD3DApp->enableFullScreen(g_pD3DApp->getWindowed());
				}
			}
			break;
		//Triggers when the user presses the 'X' button in the top
		//right corner of the sceen.
		case WM_CLOSE:
			DestroyWindow(hWnd);
			break;

		//Triggers when the Window is destroyed.
		case WM_DESTROY:
			PostQuitMessage(0);
			break;
	}

	return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

//Definition of the Constructor
Application::Application(HINSTANCE hInstance, int nCmdShow, D3DDEVTYPE devType, DWORD requestedVP) 
{
	hMainhInstance			= hInstance;
	hMainhWnd				= NULL;
	m_pD3DObject			= NULL;
	m_DevType				= devType;
	m_RequestVP				= requestedVP;
	m_Manager				= NULL;
	m_pCurrentScreen		= NULL;
	m_bIsWindowed			= false;
	ZeroMemory(&m_D3Dpp, sizeof(m_D3Dpp));

	if(!initWindow(nCmdShow)) 
	{
		MessageBox(NULL,"FAILED to create Window", 0, 0);
		PostQuitMessage(0);
	}

	if(!initDirectX()) 
	{
		MessageBox(NULL, "FAILED to create Direct3D", NULL, NULL);
		PostQuitMessage(0);
	}

	g_pGraphics				= new cGraphics();
	g_pAssets				= new Assets();
}

//Definition of the Destructor
Application::~Application() {
	Shutdown();
}

HWND Application::getMainhWnd() 
{
	return hMainhWnd;
}

HINSTANCE Application::getMainInstance() 
{
	return hMainhInstance;
}

bool Application::getWindowed()
{
	return m_bIsWindowed;
}

void Application::setWindowed(bool bIsWindowed)
{
	m_bIsWindowed = bIsWindowed;
}

//Definition of the Initial Window
bool Application::initWindow(int nCmdShow) 
{

	//Window Class for the Window Handler
	WNDCLASSEX wcex;
	
	//Initialize Window Class
	wcex.cbSize				= sizeof(WNDCLASSEX);
	wcex.style				= CS_HREDRAW | CS_VREDRAW;
	wcex.cbClsExtra			= 0;
	wcex.cbWndExtra			= 0;
	wcex.lpfnWndProc		= MsgProc;
	wcex.hInstance			= hMainhInstance;
	wcex.hIcon				= 0;
	wcex.hCursor			= LoadCursor(NULL, IDC_ARROW);
	wcex.hbrBackground		= (HBRUSH)(COLOR_WINDOW+1);
	wcex.lpszMenuName		= 0;
	wcex.lpszClassName		= "ToG";
	wcex.hIconSm			= 0;

	//Register the class
	if(!RegisterClassEx(&wcex)) 
	{
		MessageBox(0, "RegisterClass FAILED", 0, 0);
		PostQuitMessage(0);
	}

	// Default to a Window with a client area rectangle
	RECT R = { 0, 0, 800, 640 };
	AdjustWindowRect(&R, WS_OVERLAPPEDWINDOW, false);

	//Initialize the Window Handler
	hMainhWnd = CreateWindow("ToG", "Tomb of Garnok", WS_OVERLAPPEDWINDOW, 100, 100, R.right, R.bottom,
							 NULL, NULL, hMainhInstance, NULL);
	
	if(!hMainhWnd) 
	{
		MessageBox(0, "CreateWindow FAILED", 0, 0);
		PostQuitMessage(0);
	}

	//Show and Update the Window
	ShowWindow(hMainhWnd, nCmdShow);
	UpdateWindow(hMainhWnd);

	return true;
}

//Definition of the Initialization DirectX
bool Application::initDirectX() {
	//Create the Direct3D Object
	m_pD3DObject = Direct3DCreate9(D3D_SDK_VERSION);

	//Test to see if the object was created
	if(!m_pD3DObject) 
	{
		MessageBox(NULL, "FAILED to create Direct3D Object", NULL, NULL);
		PostQuitMessage(0);
	}

	//Verify Hardware Support for Specified formats in Windowed and FullScreen mode
	D3DDISPLAYMODE mode;
	m_pD3DObject->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &mode);
	HR(m_pD3DObject->CheckDeviceType(D3DADAPTER_DEFAULT, m_DevType, mode.Format, mode.Format, true));
	HR(m_pD3DObject->CheckDeviceType(D3DADAPTER_DEFAULT, m_DevType, D3DFMT_X8R8G8B8, D3DFMT_X8R8G8B8, false));

	//Gather the Device Capabilities
	HR(m_pD3DObject->GetDeviceCaps(D3DADAPTER_DEFAULT, m_DevType, &m_Caps));
	
	//Provides information as to the Vertex Processing Capabilities
	DWORD devBehaviorFlags = 0;
	if( m_Caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT ) 
		devBehaviorFlags |= m_RequestVP;
	else
		devBehaviorFlags |= D3DCREATE_PUREDEVICE;
	
	if( m_Caps.DevCaps & D3DDEVCAPS_PUREDEVICE && 
		devBehaviorFlags & D3DCREATE_HARDWARE_VERTEXPROCESSING)
			devBehaviorFlags |= D3DCREATE_PUREDEVICE;

	//Clears the memory location for the size of the 
	//Direct3D Presentation Parameters
	ZeroMemory(&m_D3Dpp, sizeof(m_D3Dpp));

	//Setup the Direct3D Presentation Parameters
	m_D3Dpp.BackBufferWidth				= 800;
	m_D3Dpp.BackBufferHeight			= 640;
	m_D3Dpp.BackBufferFormat			= D3DFMT_UNKNOWN;
	m_D3Dpp.BackBufferCount				= 1;
	m_D3Dpp.MultiSampleType				= D3DMULTISAMPLE_NONE;
	m_D3Dpp.MultiSampleQuality			= 0;
	m_D3Dpp.SwapEffect					= D3DSWAPEFFECT_DISCARD;
	m_D3Dpp.hDeviceWindow				= hMainhWnd;
	m_D3Dpp.Windowed					= true;
	m_D3Dpp.EnableAutoDepthStencil		= true;
	m_D3Dpp.AutoDepthStencilFormat		= D3DFMT_D16;
	m_D3Dpp.Flags						= 0;
	m_D3Dpp.FullScreen_RefreshRateInHz	= D3DPRESENT_RATE_DEFAULT;
	m_D3Dpp.PresentationInterval		= D3DPRESENT_INTERVAL_IMMEDIATE;

	//Create the Direct3D Device
	HR(m_pD3DObject->CreateDevice(
		D3DADAPTER_DEFAULT,
		m_DevType,
		hMainhWnd,
		devBehaviorFlags,
		&m_D3Dpp,
		&g_pD3DDevice ));

	if(!g_pD3DDevice) 
	{
		MessageBox(NULL, "FAILED to create device", NULL, NULL);
		PostQuitMessage(0);
	}

	//Turn on Depth Buffering using the Render State
	g_pD3DDevice->SetRenderState(D3DRS_ZENABLE, true);

	//Turn off the lights using the rendering state
	g_pD3DDevice->SetRenderState(D3DRS_LIGHTING, false);

	//Initialize the Input Interface 
	g_pDInput		= new cInput(DISCL_FOREGROUND | DISCL_NONEXCLUSIVE, DISCL_FOREGROUND | DISCL_NONEXCLUSIVE, hMainhWnd, hMainhInstance);

	//Initialize the Audio Interface
	g_pAudio		= new cAudio();

	return true;
}

//Definition of the Message Loop
int Application::Run() 
{
	//Declare a Message Variable
	MSG msg;
	msg.message = WM_NULL;

	//Set the frequency of the cycles to the clock tick
	__int64 countsPerSecond	= 0;
	QueryPerformanceFrequency((LARGE_INTEGER*)&countsPerSecond);
	float secondsPerCount = 1.0f / (float)countsPerSecond;

	//Set the current timer variable to the start of the clock
	__int64 previousTime = 0;
	QueryPerformanceCounter((LARGE_INTEGER*)&previousTime);

	//Message Loop
	while(msg.message != WM_QUIT) {
		if(PeekMessage(&msg, NULL, NULL, NULL, PM_REMOVE)) 
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		} 

		if(isDeviceLost())
		{
			Sleep(20);
			continue;
		}
		else 
		{
			//Declare and Initialize the previous time variable to subtract the change in time
			__int64 currentTime = 0;
			QueryPerformanceCounter((LARGE_INTEGER*)&currentTime);

			//Perform the calculation for the change in time by subtracting current time by the previous time
			float dt = (currentTime - previousTime) * secondsPerCount;

			//Game Update
			updateScene(dt);

			//Game Render
			renderScene();

			//Restarts the cycle process for the loop
			previousTime = currentTime;
		}
	}

	//Unregister the class
	UnregisterClass("ToG", hMainhInstance);

	//Returns the closing message
	return (int)msg.wParam;
}

bool Application::isDeviceLost()
{
	//Returns true if lost, false otherwise

	//Get the state of the graphics device 
	HRESULT hr = g_pD3DDevice->TestCooperativeLevel();

	//If the device is lost and cannot be reset yet, then
	//sleep for a bit and we'll try again on the 
	//next meesage loop cycle
	if(hr == D3DERR_DEVICELOST)
	{
		Sleep(20);
		return true;
	}
	//Driver error, exit
	else if(hr == D3DERR_DRIVERINTERNALERROR)
	{
		MessageBox(0, "Internal Driver Error...Exiting", 0, 0);
		PostQuitMessage(0);
		return true;
	}
	//The device is lost but we can reset and restore it.
	else if(hr == D3DERR_DEVICENOTRESET)
	{
		onLostDevice();
		HR(g_pD3DDevice->Reset(&m_D3Dpp));
		onResetDevice();
		//Not Lost Anymore
		return false;
	}
	//Not Lost Anymore
	else 
	{
		return false;
	}
}

void Application::enableFullScreen(bool bIsWindowed)
{
	if(bIsWindowed)
	{
		//Check to see if we are in Fullscreen mode
		if(!m_D3Dpp.Windowed)
		{
			return;
		}

		//Get's the current screen resolution
		m_ScreenWidth				= GetSystemMetrics(SM_CXSCREEN);
		m_ScreenHeight				= GetSystemMetrics(SM_CYSCREEN);

		m_D3Dpp.BackBufferFormat	= D3DFMT_X8R8G8B8;
		m_D3Dpp.BackBufferWidth		= m_ScreenWidth;
		m_D3Dpp.BackBufferHeight	= m_ScreenHeight;
		m_D3Dpp.Windowed			= false;

		//Change the window style to a more friendly style
		SetWindowLongPtr(hMainhWnd, GWL_STYLE, WS_POPUP);

		//If we call SetWindowLongPtr, MSDN states that we need
		//to call SetWindowPos for the change to take effect.
		//In addition, we need to call this function anyway t
		//to update the window dimensions
		SetWindowPos(hMainhWnd, HWND_TOP, 0, 0,
			m_ScreenWidth, m_ScreenHeight, SWP_NOZORDER | SWP_SHOWWINDOW);

	}
	else
	{
		//Check to see if we are in Windowed mode
		if(m_D3Dpp.Windowed)
		{
			return;
		}

		//Default to a client rectangle of 800x600
		RECT R = {0, 0, 800, 600};
		AdjustWindowRect(&R, WS_OVERLAPPEDWINDOW, false);
		m_D3Dpp.BackBufferFormat	= D3DFMT_UNKNOWN;
		m_D3Dpp.BackBufferWidth		= 800;
		m_D3Dpp.BackBufferHeight	= 600;
		m_D3Dpp.Windowed			= true;
	
		//Change the window style to a more windowed
		//friendly style.
		SetWindowLongPtr(hMainhWnd, GWL_STYLE, WS_OVERLAPPEDWINDOW);

		SetWindowPos(hMainhWnd, HWND_TOP, 100, 100,
			R.right, R.bottom, SWP_NOZORDER | SWP_SHOWWINDOW);
	}

	//Reset the device with the changes.
	onLostDevice();
	//HR(g_pD3DDevice->Reset(&m_D3Dpp));
	onResetDevice();
}

void Application::Shutdown() 
{
	//Set the address the pointer is pointing to as NULL
	//this should stop the process of a memory leak
	m_pCurrentScreen = NULL;

	//Releases the information inside of the Input Interface
	delete g_pDInput;
	delete g_pAudio;
	delete g_pAssets;
	delete g_pGraphics;

	//Releases the COM objects in the reserve order
	SAFE_RELEASE(g_pD3DDevice);
	SAFE_RELEASE(m_pD3DObject);
}